package com.seelyn.rhea.mvc.internal;

import com.seelyn.rhea.mvc.common.utils.CharsetConst;
import com.seelyn.rhea.mvc.context.Request;
import com.seelyn.rhea.mvc.context.Session;
import com.seelyn.rhea.mvc.internal.enums.HttpMethod;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.InvocationTargetException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * @author slacrey
 * @since 2016/1/23
 */
public class InternalRequest implements Request {

    private static final Logger LOG = LoggerFactory.getLogger(InternalRequest.class);

    private static final String HTTP_METHOD = "_method_";
    private static final String USER_AGENT = "user-agent";
    public static final byte[] BYTES = new byte[0];

    private final Map<String, String> params = new HashMap<>();

    private final HttpServletRequest servletRequest;

    private Session session;

    private String body;

    private byte[] bodyAsBytes;

    private Set<String> headers;

    private final String uri;
    private final String requestMethod;

    public static Request build(HttpServletRequest request) {
        return new InternalRequest(request);
    }

    InternalRequest(HttpServletRequest request) {
        servletRequest = request;
        uri = request.getRequestURI().substring(request.getContextPath().length());
        if ("POST".equalsIgnoreCase(servletRequest.getMethod())) {
            String[] httpMethods = servletRequest.getParameterValues(HTTP_METHOD);
            if (httpMethods != null) {
                HttpMethod method = HttpMethod.hasName(httpMethods[0]);
                if (method != null) {
                    requestMethod = method.name();
                } else {
                    requestMethod = servletRequest.getMethod();
                }
            } else {
                requestMethod = servletRequest.getMethod();
            }
        } else {
            requestMethod = servletRequest.getMethod();
        }
    }

    @Override public Map<String, String> params() {
        return Collections.unmodifiableMap(params);
    }

    @Override public String params(String param) {
        if (param == null) {
            return null;
        }
        return params.get(param);
    }

    @Override public void params(String key, String value) {
        if (key != null && value != null) {
            params.put(key, value);
        }
    }

    @Override public String requestMethod() {
        return requestMethod;
    }

    @Override public String beforeFilter() {
        return HttpMethod.BEFORE.name();
    }

    @Override public String afterFilter() {
        return HttpMethod.AFTER.name();
    }

    @Override public String scheme() {
        return servletRequest.getScheme();
    }

    @Override public String host() {
        return servletRequest.getHeader("host");
    }

    @Override public String userAgent() {
        return servletRequest.getHeader(USER_AGENT);
    }

    @Override public int port() {
        return servletRequest.getServerPort();
    }

    @Override public String pathInfo() {
        return servletRequest.getPathInfo();
    }

    @Override public String servletPath() {
        return servletRequest.getServletPath();
    }

    @Override public String contextPath() {
        return servletRequest.getContextPath();
    }

    @Override public String url() {
        return servletRequest.getRequestURL().toString();
    }

    @Override public String contentType() {
        return servletRequest.getContentType();
    }

    @Override public String remoteIp() {
        return servletRequest.getRemoteAddr();
    }

    @Override public String body() {
        if (body == null) {
            body = new String(bodyAsBytes(), CharsetConst.UTF_8);
        }
        return body;
    }

    @Override public byte[] bodyAsBytes() {
        if (bodyAsBytes == null) {
            readBodyAsBytes();
        }
        return bodyAsBytes;
    }

    private void readBodyAsBytes() {
        try {
            bodyAsBytes = IOUtils.toByteArray(servletRequest.getInputStream());
        } catch (Exception e) {
            LOG.warn("Exception when reading body", e);
        }
    }

    @Override public int contentLength() {
        return servletRequest.getContentLength();
    }

    @Override public String queryParams(String queryParam) {
        return servletRequest.getParameter(queryParam);
    }

    @Override public String[] queryParamsValues(String queryParam) {
        return servletRequest.getParameterValues(queryParam);
    }

    @Override public String headers(String header) {
        return servletRequest.getHeader(header);
    }

    @Override public Set<String> queryParams() {
        return servletRequest.getParameterMap().keySet();
    }

    @Override public Set<String> headers() {
        if (headers == null) {
            headers = new TreeSet<>();
            Enumeration<String> enumeration = servletRequest.getHeaderNames();
            while (enumeration.hasMoreElements()) {
                headers.add(enumeration.nextElement());
            }
        }
        return headers;
    }

    @Override public String queryString() {
        return servletRequest.getQueryString();
    }

    @Override public void attribute(String attribute, Object value) {
        servletRequest.setAttribute(attribute, value);
    }

    @Override public Object attribute(String attribute) {
        return servletRequest.getAttribute(attribute);
    }

    @Override public Set<String> attributes() {
        Set<String> attrList = new HashSet<>();
        Enumeration<String> attributes = servletRequest.getAttributeNames();
        while (attributes.hasMoreElements()) {
            attrList.add(attributes.nextElement());
        }
        return attrList;
    }

    @Override public HttpServletRequest raw() {
        return servletRequest;
    }


    @Override public Session session() {
        if (session == null) {
            session = new InternalSession(servletRequest.getSession());
        }
        return session;
    }

    @Override public Session session(boolean create) {
        if (session == null) {
            HttpSession httpSession = servletRequest.getSession(create);
            if (httpSession != null) {
                session = new InternalSession(httpSession);
            }
        }
        return session;
    }

    @Override public Map<String, String> cookies() {
        Map<String, String> result = new HashMap<>();
        Cookie[] cookies = servletRequest.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                result.put(cookie.getName(), cookie.getValue());
            }
        }
        return result;
    }

    @Override public String cookie(String name) {
        Cookie[] cookies = servletRequest.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(name)) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    @Override public String uri() {
        return uri;
    }

    @Override public String protocol() {
        return servletRequest.getProtocol();
    }

    @Override public <T> void toBean(T t) {
        try {
            BeanUtils.populate(t, servletRequest.getParameterMap());
        } catch (IllegalAccessException | InvocationTargetException e) {
            LOG.error(e.getMessage(), e);
        }
    }

}
